#include "abstract-database-client.h"
#include "abstract-queries.h"
#include "container-helper.h"
#include "debug-helper.h"
#include "sm-global.h"
#include "graph-node.h"
#include "meta-types.h"
#include "model-result.h"
#include "standard-database-model.h"
#include "value-list-result.h"
#include "value-result.h"

StandardDatabaseModel::StandardDatabaseModel(QObject *parent) :
    AbstractSectionModel(parent), m_databaseClient(0), m_internalModel(0),
    m_queries(0), m_idColumnCount(1)
{
}

void StandardDatabaseModel::loadData()
{
    Q_ASSERT(m_queries);
    Q_ASSERT(m_databaseClient);
    ModelResult* result = m_databaseClient->selectModel(m_queries->selectQuery(0, 0), m_loadingParameters);
    connect (result, &ModelResult::resultReady, this, &StandardDatabaseModel::finishLoadData);
}

void StandardDatabaseModel::finishLoadData (ModelResult* result)
{
    result->deleteLater();
    if (result->hasError())
    {
        qdebug("load data error" << result->error());
        emit loadDataFinished(false);
        return;
    }

    Q_ASSERT(result->model());
    beginResetModel();
    delete m_internalModel;
    m_internalModel = result->model();
    m_internalModel->setParent(this);
    setupInternalModel();
    setupInternalModelHeader();
    endResetModel();
    emit loadDataFinished(true);
}

void StandardDatabaseModel::setupInternalModel()
{
    for (int i = 0; i < m_internalModel->rowCount(); i++)
    {
        for (int j = 0; j < m_internalModel->columnCount(); j++)
        {
            m_internalModel->setData(m_internalModel->index(i, j),
                             m_internalModel->index(i, j).data(), Qt::UserRole);
        }
    }
}

void StandardDatabaseModel::setupInternalModelHeader()
{
    if (m_headerSections.isEmpty())
        return;
    if (m_internalModel->columnCount() != m_headerSections.size() + m_idColumnCount)
        m_internalModel->insertColumns(0, m_headerSections.size() + m_idColumnCount);

    for (int j = 0; j < m_headerSections.size(); j++)
    {
        m_internalModel->setHeaderData(j + m_idColumnCount, Qt::Horizontal, m_headerSections.at(j), Qt::DisplayRole);
    }
}

QVariant StandardDatabaseModel::id (int row, int column) const
{
    if (row < 0 || row >= rowCount())
        return -1;

    return m_internalModel->index(row, column).data();
}

ValueResult *StandardDatabaseModel::canAddData (QVariantList dataList)
{
    Q_ASSERT(dataList.size() == columnCount());
    for (int i = 0; i < dataList.size(); i++)
    {
        ValueResult* result = canSetField(dataList.at(i), i);
        if (result->hasError())
            return result;
        else
            delete result;
    }

    return canSetRow(dataList);
}

ValueResult *StandardDatabaseModel::canUpdateData (QVariant value, int row, int column)
{
    ValueResult* result = canSetField(value, column, row);
    Q_ASSERT(result);
    // assume canSetField is synchronous
    if (result->hasError())
        return result;
    else
        delete result;

    QVariantList dataList;
    for (int i = 0; i < column; i++)
        dataList << index(row, i).data(Qt::UserRole);
    dataList << value;
    for (int i = column+1; i < columnCount(); i++)
        dataList << index(row, i).data(Qt::UserRole);

    return canSetRow(dataList, row);
}

QMap<int,QAbstractItemDelegate*> StandardDatabaseModel::delegates() const
{
    return m_delegates;
}

ValueResult* StandardDatabaseModel::canSetRow (QVariantList dataList, int row)
{
    Q_UNUSED(row);
    ValueResult* result = m_databaseClient->selectExists(m_queries->existsQuery(), dataList);
    connect (result, &ValueResult::resultReady, this, &StandardDatabaseModel::finishCanSetRow);
    ValueResult* outerResult = new ValueResult(this);
    result->setRequestData(QVariant::fromValue(outerResult));
    return outerResult;
}

void StandardDatabaseModel::finishCanSetRow (ValueResult* result)
{
    Q_ASSERT(result);
    result->deleteLater();
    ValueResult* outerResult = result->requestData().value<ValueResult*>();
    if (result->hasError())
    {
        outerResult->setError("Wystąpił niespodziewany błąd.", SmGlobal::UnknownDatabaseError);
    }
    else
    {
        if (result->value().toBool())
            outerResult->setError(m_dataAlreadyExistText, SmGlobal::DataInvalidError);
        else
            outerResult->setValue(true);
    }
}

int StandardDatabaseModel::rowCount(const QModelIndex &parent) const
{
    if (m_internalModel)
        return m_internalModel->rowCount(parent);
    else
        return 0;
}

int StandardDatabaseModel::columnCount(const QModelIndex &parent) const
{
    if (m_internalModel)
        return m_internalModel->columnCount(parent) - m_idColumnCount;
    else
        return 0;
}

QVariant StandardDatabaseModel::data(const QModelIndex &index, int role) const
{
    if (m_internalModel)
        return m_internalModel->data(m_internalModel->index(index.row(),
                                index.column() + m_idColumnCount), role);
    else
        return QVariant();
}

QVariant StandardDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (!m_internalModel)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return m_internalModel->headerData(section + m_idColumnCount, orientation, role);
    else
        return m_internalModel->headerData(section, orientation, role);
}

bool StandardDatabaseModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    Q_UNUSED(role);
    QVariant correctedValue = this->correctedValue(value, index.column());
    ValueResult* result = canUpdateData(correctedValue, index.row(), index.column());
    result->setRequestData(QVariantList() << correctedValue << index.row() << index.column());
    connect (result, &ValueResult::resultReady, this, &StandardDatabaseModel::finishSetData);
    return true;
}

void StandardDatabaseModel::finishSetData (ValueResult* result)
{
    result->deleteLater();
    if (result->hasError())
    {
        emit notifyHint("Nie można edytować danych.", result->error().text());
        return;
    }
    QVariantList dataList = result->requestData().toList();
    updateData(dataList.at(0), dataList.at(1).toInt(), dataList.at(2).toInt());
}

SectionInfo StandardDatabaseModel::sectionInfo() const
{
    SectionInfo info;
    info.setDatabaseClient(m_databaseClient);
    info.setLoadingParameters(m_loadingParameters);
    return info;
}

void StandardDatabaseModel::setSectionInfo (const SectionInfo& sectionInfo)
{
    m_databaseClient = sectionInfo.databaseClient();
    m_loadingParameters = sectionInfo.loadingParameters();
}

void StandardDatabaseModel::setLoadingParameters (QVariantList parameters)
{
    m_loadingParameters = parameters;
}

void StandardDatabaseModel::setIdColumnCount(int idColumnCount)
{
    m_idColumnCount = idColumnCount;
}

void StandardDatabaseModel::initialize()
{
    Q_ASSERT(m_queries);
    m_queries->initialize();
    emit initialized();
}

void StandardDatabaseModel::setupQueries()
{
}

void StandardDatabaseModel::addData (QVariantList dataList)
{
    ValueResult* result = m_databaseClient->insertData(m_queries->insertQuery(0, 1), dataList);
    connect (result, &ValueResult::resultReady, this, &StandardDatabaseModel::finishAddData);
}

void StandardDatabaseModel::finishAddData (ValueResult* result)
{
    result->deleteLater();
    if (result->hasError())
    {
        qdebug("add data error" << result->error());
        emit notifyWarning("Błąd dodawania danych.", result->error().text());
        emit addDataFinished(false);
        return;
    }

    if (m_reloadAfterModification)
        loadData();
    emit addDataFinished(true);
}

void StandardDatabaseModel::updateData (QVariant data, int row, int column)
{
    QVariantList dataList;
    dataList << id(row);
    for (int i = 0; i < column; i++)
        dataList << index(row, i).data(Qt::UserRole);
    dataList << data;
    for (int i = column+1; i < columnCount(); i++)
        dataList << index(row, i).data(Qt::UserRole);

    ValueResult* result = m_databaseClient->updateData(m_queries->updateQuery(0, 0), dataList);
    connect (result, SIGNAL(resultReady(ValueResult*)), this, SLOT(finishUpdateData(ValueResult*)));
}

void StandardDatabaseModel::finishUpdateData (ValueResult* result)
{
    result->deleteLater();
    if (result->hasError())
    {
        qdebug("update data error" << result->error());
        emit notifyWarning("Błąd edycji danych", result->error().text());
        emit updateDataFinished(false);
        return;
    }

    if (m_reloadAfterModification)
        loadData();

    emit updateDataFinished(true);
}

void StandardDatabaseModel::removeData (QVariantList dataList)
{
    ValueResult* result = m_databaseClient->removeData(m_queries->deleteQuery(0, dataList.size()), dataList);
    connect (result, &ValueResult::resultReady, this, &StandardDatabaseModel::finishRemoveData);
}

void StandardDatabaseModel::finishRemoveData (ValueResult* result)
{
    result->deleteLater();
    if (result->hasError())
    {
        qdebug("remove data error" << result->error());
        emit notifyWarning("Błąd usuwania danych", result->error().text());
        emit removeDataFinished(false);
        return;
    }

    if (m_reloadAfterModification)
        loadData();
    emit removeDataFinished(true);
}

void StandardDatabaseModel::removeData (QVariant data)
{
    removeData(QVariantList() << data);
}
